package cn.ycc1.baseset.exceptions;

/**
 * Unchecked Exceptions — The Controversy
 * @author ycc
 * @date 2025/3/7
 */
public class UncheckedExceptions {
    /**
     * Unchecked Exceptions — The Controversy
     * Because the Java programming language does not require methods to catch or to specify unchecked exceptions
     * (RuntimeException, Error, and their subclasses), programmers may be tempted to write code that throws only unchecked
     * exceptions or to make all their exception subclasses inherit from RuntimeException. Both of these shortcuts allow
     * programmers to write code without bothering with compiler errors and without bothering to specify or to catch any
     * exceptions. Although this may seem convenient to the programmer, it sidesteps the intent of the catch or specify
     * requirement and can cause problems for others using your classes.
     *
     * Why did the designers decide to force a method to specify all uncaught checked exceptions that can be thrown within its
     * scope? Any Exception that can be thrown by a method is part of the method's public programming interface.
     * Those who call a method must know about the exceptions that a method can throw so that they can decide what to do
     * about them. These exceptions are as much a part of that method's programming interface as its parameters and return
     * value.
     *
     * The next question might be: "If it's so good to document a method's API, including the exceptions it can throw,
     * why not specify runtime exceptions too?" Runtime exceptions represent problems that are the result of a programming
     * problem, and as such, the API client code cannot reasonably be expected to recover from them or to handle them in
     * any way. Such problems include arithmetic exceptions, such as dividing by zero; pointer exceptions, such as trying to
     * access an object through a null reference; and indexing exceptions, such as attempting to access an array element
     * through an index that is too large or too small.
     *
     * Runtime exceptions can occur anywhere in a program, and in a typical one they can be very numerous. Having to add
     * runtime exceptions in every method declaration would reduce a program's clarity. Thus, the compiler does not require
     * that you catch or specify runtime exceptions (although you can).
     *
     * One case where it is common practice to throw a RuntimeException is when the user calls a method incorrectly. For example,
     * a method can check if one of its arguments is incorrectly null. If an argument is null, the method might throw a
     * NullPointerException, which is an unchecked exception.
     *
     * Generally speaking, do not throw a RuntimeException or create a subclass of RuntimeException simply because you
     * don't want to be bothered with specifying the exceptions your methods can throw.
     *
     * Here's the bottom line guideline: If a client can reasonably be expected to recover from an exception, make it a
     * checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception.
     */


    /**
     * Advantages of Exceptions
     * Now that you know what exceptions are and how to use them, it's time to learn the advantages of using exceptions in
     * your programs.
     *
     * Advantage 1: Separating Error-Handling Code from "Regular" Code
     * Exceptions provide the means to separate the details of what to do when something out of the ordinary happens from the
     * main logic of a program. In traditional programming, error detection, reporting, and handling often lead to confusing
     * spaghetti code. For example, consider the pseudocode method here that reads an entire file into memory.
     *
     * readFile {
     *     open the file;
     *     determine its size;
     *     allocate that much memory;
     *     read the file into memory;
     *     close the file;
     * }
     * At first glance, this function seems simple enough, but it ignores all the following potential errors.
     *
     * What happens if the file can't be opened?
     * What happens if the length of the file can't be determined?
     * What happens if enough memory can't be allocated?
     * What happens if the read fails?
     * What happens if the file can't be closed?
     * To handle such cases, the readFile function must have more code to do error detection, reporting, and handling.
     * Here is an example of what the function might look like.
     *
     * errorCodeType readFile {
     *     initialize errorCode = 0;
     *
     *     open the file;
     *     if (theFileIsOpen) {
     *         determine the length of the file;
     *         if (gotTheFileLength) {
     *             allocate that much memory;
     *             if (gotEnoughMemory) {
     *                 read the file into memory;
     *                 if (readFailed) {
     *                     errorCode = -1;
     *                 }
     *             } else {
     *                 errorCode = -2;
     *             }
     *         } else {
     *             errorCode = -3;
     *         }
     *         close the file;
     *         if (theFileDidntClose && errorCode == 0) {
     *             errorCode = -4;
     *         } else {
     *             errorCode = errorCode and -4;
     *         }
     *     } else {
     *         errorCode = -5;
     *     }
     *     return errorCode;
     * }
     * There's so much error detection, reporting, and returning here that the original seven lines of code are lost in
     * the clutter. Worse yet, the logical flow of the code has also been lost, thus making it difficult to tell whether
     * the code is doing the right thing: Is the file really being closed if the function fails to allocate enough memory?
     * It's even more difficult to ensure that the code continues to do the right thing when you modify the method three
     * months after writing it. Many programmers solve this problem by simply ignoring it — errors are reported when their
     * programs crash.
     *
     * Exceptions enable you to write the main flow of your code and to deal with the exceptional cases elsewhere.
     * If the readFile function used exceptions instead of traditional error-management techniques, it would look more like
     * the following.
     *
     * readFile {
     *     try {
     *         open the file;
     *         determine its size;
     *         allocate that much memory;
     *         read the file into memory;
     *         close the file;
     *     } catch (fileOpenFailed) {
     *        doSomething;
     *     } catch (sizeDeterminationFailed) {
     *         doSomething;
     *     } catch (memoryAllocationFailed) {
     *         doSomething;
     *     } catch (readFailed) {
     *         doSomething;
     *     } catch (fileCloseFailed) {
     *         doSomething;
     *     }
     * }
     * Note that exceptions don't spare you the effort of doing the work of detecting, reporting, and handling errors,
     * but they do help you organize the work more effectively.
     *
     * Advantage 2: Propagating Errors Up the Call Stack
     * A second advantage of exceptions is the ability to propagate error reporting up the call stack of methods. Suppose
     * that the readFile method is the fourth method in a series of nested method calls made by the main program: method1
     * calls method2, which calls method3, which finally calls readFile.
     *
     * method1 {
     *     call method2;
     * }
     *
     * method2 {
     *     call method3;
     * }
     *
     * method3 {
     *     call readFile;
     * }
     * Suppose also that method1 is the only method interested in the errors that might occur within readFile. Traditional
     * error-notification techniques force method2 and method3 to propagate the error codes returned by readFile up the call
     * stack until the error codes finally reach method1—the only method that is interested in them.
     *
     * method1 {
     *     errorCodeType error;
     *     error = call method2;
     *     if (error)
     *         doErrorProcessing;
     *     else
     *         proceed;
     * }
     *
     * errorCodeType method2 {
     *     errorCodeType error;
     *     error = call method3;
     *     if (error)
     *         return error;
     *     else
     *         proceed;
     * }
     *
     * errorCodeType method3 {
     *     errorCodeType error;
     *     error = call readFile;
     *     if (error)
     *         return error;
     *     else
     *         proceed;
     * }
     * Recall that the Java runtime environment searches backward through the call stack to find any methods that are
     * interested in handling a particular exception. A method can duck any exceptions thrown within it, thereby allowing
     * a method farther up the call stack to catch it. Hence, only the methods that care about errors have to worry about
     * detecting errors.
     *
     * method1 {
     *     try {
     *         call method2;
     *     } catch (exception e) {
     *         doErrorProcessing;
     *     }
     * }
     *
     * method2 throws exception {
     *     call method3;
     * }
     *
     * method3 throws exception {
     *     call readFile;
     * }
     * However, as the pseudocode shows, ducking an exception requires some effort on the part of the middleman methods.
     * Any checked exceptions that can be thrown within a method must be specified in its throws clause.
     *
     * Advantage 3: Grouping and Differentiating Error Types
     * Because all exceptions thrown within a program are objects, the grouping or categorizing of exceptions is a natural
     * outcome of the class hierarchy. An example of a group of related exception classes in the Java platform are those
     * defined in java.io — IOException and its descendants. IOException is the most general and represents any type of error
     * that can occur when performing I/O. Its descendants represent more specific errors. For example, FileNotFoundException
     * means that a file could not be located on disk.
     *
     * A method can write specific handlers that can handle a very specific exception. The FileNotFoundException class has no
     * descendants, so the following handler can handle only one type of exception.
     *
     * catch (FileNotFoundException e) {
     *     ...
     * }
     * A method can catch an exception based on its group or general type by specifying any of the exception's superclasses
     * in the catch statement. For example, to catch all I/O exceptions, regardless of their specific type, an exception handler
     * specifies an IOException argument.
     *
     * catch (IOException e) {
     *     ...
     * }
     * This handler will be able to catch all I/O exceptions, including FileNotFoundException, EOFException, and so on.
     * You can find details about what occurred by querying the argument passed to the exception handler. For example,
     * use the following to print the stack trace.
     *
     * catch (IOException e) {
     *     // Output goes to System.err.
     *     e.printStackTrace();
     *     // Send trace to stdout.
     *     e.printStackTrace(System.out);
     * }
     * You could even set up an exception handler that handles any Exception with the handler here.
     *
     * // A (too) general exception handler
     * catch (Exception e) {
     *     ...
     * }
     * The Exception class is close to the top of the Throwable class hierarchy. Therefore, this handler will catch many other
     * exceptions in addition to those that the handler is intended to catch. You may want to handle exceptions this way
     * if all you want your program to do, for example, is print out an error message for the user and then exit.
     *
     * In most situations, however, you want exception handlers to be as specific as possible. The reason is that the first
     * thing a handler must do is determine what type of exception occurred before it can decide on the best recovery strategy.
     * In effect, by not catching specific errors, the handler must accommodate any possibility. Exception handlers that are
     * too general can make code more error-prone by catching and handling exceptions that weren't anticipated by the programmer
     * and for which the handler was not intended.
     *
     * As noted, you can create groups of exceptions and handle exceptions in a general fashion, or you can use the specific
     * exception type to differentiate exceptions and handle exceptions in an exact fashion.
     */

    /**
     * Summary
     * A program can use exceptions to indicate that an error occurred. To throw an exception, use the throw statement and
     * provide it with an exception object — a descendant of Throwable — to provide information about the specific error that
     * occurred. A method that throws an uncaught, checked exception must include a throws clause in its declaration.
     *
     * A program can catch exceptions by using a combination of the try, catch, and finally blocks.
     *
     * The try block identifies a block of code in which an exception can occur.
     * The catch block identifies a block of code, known as an exception handler, that can handle a particular type of
     * exception.
     * The finally block identifies a block of code that is guaranteed to execute, and is the right place to close files,
     * recover resources, and otherwise clean up after the code enclosed in the try block.
     * The try statement should contain at least one catch block or a finally block and may have multiple catch blocks.
     *
     * The class of the exception object indicates the type of exception thrown. The exception object can contain further
     * information about the error, including an error message. With exception chaining, an exception can point to the
     * exception that caused it, which can in turn point to the exception that caused it, and so on.
     */
}
